<?php
/**
 * Created by PhpStorm.
 * User: wangjie
 * Date: 2020/11/30
 * Time: 11:46 AM
 */
namespace App\Api\Controllers\DongGuan;


use App\Models\DongguanConfig;
use function EasyWeChat\Kernel\Support\get_server_ip;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * 接入东莞银行开放平台的通讯协议为 HTTPS，请求方用 POST 方法发送数据，请求/响应数据为JSON 格式字符串，一律使用UTF-8字符集
 * Class BaseController
 * @package App\Api\Controllers\DongGuan
 */
class BaseController
{
    public $postCharset = "UTF-8"; //
    public $fileCharset = "UTF-8"; //

    private $url = 'https://kfyh.dgcb.com.cn:49003/gateway/'; //测试地址
//    private $url = 'https://kfyh.dongguanbank.cn:19003/gateway/'; //生产地址

    public $applicationKey = 'BODBE00001'; //申请密钥
    public $getToken = 'BODBE00002'; //获取token

    public $weChatUrl = 'BODPY00004'; //微信支付
    public $aliUrl = 'BODPY00005'; //支付宝支付
    public $orderQuery = 'BODPY10001'; //订单查询
    public $orderRefund = 'BODPY00006'; //退款
    public $orderRefundQuery = 'BODPY10002'; //退款查询

    public $orderInfoCreate = 'BODPY00007'; //账单文件生成
    public $fileDownload = 'BODFL00000'; //文件下载地址


    public function __construct($data)
    {
        //申请密钥
        $appId = $data['appId'];
        $configId = $data['config_id'];
        $dongguan_config_obj = DongguanConfig::where('config_id', $configId)->first();
        if (!$dongguan_config_obj) {
            $dongguan_config_obj = DongguanConfig::where('config_id', '1234')->first();
        }

        if ($dongguan_config_obj) {
            if ( empty($dongguan_config_obj->selfPublicKey) && empty($dongguan_config_obj->selfPrivateKey) && empty($dongguan_config_obj->BODPublicKey)) {
                $applyKeyRes = $this->applyKey($appId, $configId); //true false
                if (!$applyKeyRes) {
                    Log::info('东莞银行-申请密钥-获取失败');
                }
            }
        } else {
            Log::info('东莞银行-支付未配置');
            Log::info($configId);
        }

        //获取token
        $res_token = $this->get_token($appId);
        if (!$res_token) {
            Log::info('东莞银行-获取token-失败');
        }
    }


    /**
     * @param $data
     * @param string $Url
     * @param string $appId    应用id,合作方应用审核通过后下发
     * @param string $appSecret    应用密钥,合作方应用审核通过后下发
     * @param string $selfPrivateKey   合作方私钥
     * @param string $publicKey    东莞银行开放银行公钥
     * @return array
     */
    public function execute($data, $Url, $appId, $appSecret, $selfPrivateKey, $publicKey)
    {
        $reqData = [
            'clientID' => get_server_ip(),
            'nonce' => $this->nonceStr(),
            'timestamp' => $this->micTime(), //时间戳,精确到毫秒的请求发送时间,格式yyyyMMddhhmmssSSS
            'appDeviceId' => $appId, //客户端唯一标识,h5访问时送手机唯一标识,其他可送appId
        ];
        $reqData['ciphertext'] = ['bizContent' => $this->encryption($data, $publicKey)];
        $reqData['token'] = $this->get_token($appId); //客户端会话标识,申请密钥和获取token时为空
        $reqData['signature'] = $this->rsaSign($reqData, $selfPrivateKey); //报文签名串,申请密钥和申请token时为空

        $Url = $this->url.$Url;
        $ch = curl_init($Url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        $reqJsonData = json_encode($reqData);
        Log::info('东莞银行-原始请求参数');
        Log::info($reqJsonData);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $reqJsonData); //JSON类型字符串
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取的信息以文件流的形式返回
        $header = [
            'Content-Type=application/json;charset=UTF-8',
            'Content-Length='.strlen($data),
            'appId='.$appId,
            'appSecret='.$appSecret
        ];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        $res = curl_exec($ch);
        $response = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($res == NULL) {
            curl_close($ch);
            return [
                'status' => -1,
                'message' => '系统错误'
            ];
        } else if ($response != "200") {
            curl_close($ch);
            return [
                'status' => -1,
                'message' => '通讯异常'
            ];
        }

        curl_close($ch);
        $result = json_decode($res, true);
        if (isset($result['signature']) && !empty($result['signature'])) {
            $rsaVer = $this->rsaVerify($reqData, $result['signature'], $BODPublicKey);
            if (!$rsaVer) {
                return [
                    'status' => 2,
                    'message' => '莞银通-验签失败'
                ];
            }
        }

        if ($result['errorCode'] == '0000') {
            $bizContent = isset($result['ciphertext']) ? $result['ciphertext']: '';
            if ($bizContent) {
                $result['bizContent'] = $this->decrypt($bizContent, $selfPrivateKey);
            }
            return [
                'status' => 1,
                'message' => isset($result['errorDesc']) ? $result['errorDesc']: $this->getMessageByCode($result['errorCode']),
                'data' => $result
            ];
        } else {

        }
    }


    /**
     * 参数拼接
     * @param $params array 要拼接的参数
     * @param $access_key string sSaaS平台应用密钥
     * @return string
     */
    public function getSignContent($params, $access_key)
    {
        ksort($params);
        $string_2_sign = "";
        $i = 0;

        foreach ($params as $key => $values) {
            if (($this->checkEmpty($values) === false)  && ("@" != substr($values, 0, 1))) {
                // 转换成目标字符集
                $values = $this->character($values, $this->postCharset);

                if ($i == 0) {
                    $string_2_sign .= "$key" . "=" . "$values";
                } else {
                    $string_2_sign .= "&" . "$key" . "=" . "$values";
                }

                $i++;
            }
        }

        unset ($key, $values);
        return strtoupper(md5($string_2_sign.$access_key));
    }


    /**
     * 校验$value是否非空
     * if not set ,return true;
     * if is null , return true;
     * @param $value mixed 检查的字段
     * @return bool
     */
    protected function checkEmpty($value)
    {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }


    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    function character($data, $targetCharset)
    {
        if (!empty($data)) {
            $fileType = $this->fileCharset;
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset);
            }
        }

        return $data;
    }


    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param $para
     * @return string
     */
    function createLinkString($para)
    {
        $params = [];
        foreach ($para as $key => $value) {
            if (is_array($value)) {
                $value = stripslashes(json_encode($value, JSON_UNESCAPED_UNICODE));
            }
            $params[] = $key . '=' . $value;
        }
        $data = implode("&", $params);

        return $data;
    }


    /**
     * 对数组排序
     * @param $para
     * @return mixed
     */
    function argSort($para)
    {
        ksort($para);

        return $para;
    }


    /**
     * RSA签名
     * @param string $data 待签名数据
     * @param string $selfPrivateKey 合作方私钥
     * @return string
     */
    function rsaSign($data, $selfPrivateKey)
    {
        $data = $this->argSort($data);

        $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($selfPrivateKey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        $res = openssl_get_privatekey($privateKey);
        openssl_sign($data, $sign, $res);
        openssl_free_key($res);
        $sign = base64_encode($sign); //base64编码

        return $sign;
    }


    /**
     * RSA验签
     * @param string $data 待签名数据
     * @param string $sign 需要验签的签名
     * @param string $BODPublicKey 东莞银行开放银行公钥
     * @return bool
     */
    function rsaVerify($data, $sign, $BODPublicKey)
    {
        if (isset($data['signature'])) {
            unset($data['signature']);
        }

        $pubKey = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($BODPublicKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        $res = openssl_get_publickey($pubKey); //转换为openssl格式密钥
        $result = (bool)openssl_verify($data, base64_decode($sign), $res); //调用openssl内置方法验签，返回bool值
        //var_dump($result);
        openssl_free_key($res);//释放资源

        return $result;//返回资源是否成功
    }


    /**
     * 传输数据加密
     * @param $data
     * @param string $publicKey 东莞银行开放银行公钥
     * @return string
     */
    function encryption($data, $publicKey)
    {
        $data = json_encode($data);
        $publicKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($publicKey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        $crypto = '';

        foreach (str_split($data, 117) as $chunk) {
            openssl_public_encrypt($chunk, $encryptData, $publicKey);

            $crypto .= $encryptData;
        }

        return base64_encode($crypto);
    }


    /**
     * 传输数据解密
     * @param string $data 编码后的字串
     * @param string $selfPrivateKey 合作方私钥
     * @return bool
     */
    function decrypt($data, $selfPrivateKey)
    {
        $privateKey = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($selfPrivateKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        $crypto = '';
        foreach (str_split(base64_decode($data), 128) as $chunk) {
            openssl_private_decrypt($chunk, $decryptData, $privateKey);
            $crypto .= $decryptData;
        }

        return json_decode($crypto, true);
    }


    /**
     * 申请密钥
     * 于获取数字签名和验签所需要的秘钥信息，包括合作方的私钥和公钥对，以及行方的公钥。本接口正常情况下，在整个合作周期只调用一次用
     *
     * @param $appId
     * @param $config_id
     * @return bool
     */
    public function applyKey($appId, $config_id = '1234')
    {
        $req_data = [
            'timestamp' => $this->micTime(),
            'nonce' => $this->nonceStr(),
            'clientID' => get_server_ip(),
            'bizContent' => [
                'devitk' => $this->getDevice($appId)
            ]
        ];
        $result = $this->curl($req_data, $this->applicationKey); // -1 系统错误  1 成功
        if ($result['status'] == 1) {
            $req_data = $result['data'];
            if ($req_data['errorCode'] == '0000') {
                if ( isset($req_data['bizContent']) ) {
                    $biz_content_data = $req_data['bizContent'];

                    $update_res = DongguanConfig::where('config_id', $config_id)->update([
                        'selfPublicKey' => $biz_content_data['selfPublicKey'],
                        'selfPrivateKey' => $biz_content_data['selfPrivateKey'],
                        'BODPublicKey' => $biz_content_data['BODPublicKey'],
                    ]);
                    if (!$update_res) {
                        Log::info('东莞银行-申请密钥-入库更新失败');
                        Log::info($biz_content_data);
                        return false;
                    }

                    return $biz_content_data;
                } else {
                    Log::info('东莞银行-申请密钥-参数未返回');
                    Log::info($req_data);
                    return false;
                }
            } else {
                Log::info('东莞银行-申请密钥-失败');
                Log::info($req_data);
                return false;
            }
        } else {
            Log::info('东莞银行-申请密钥-error');
            Log::info($result);
            return false;
        }
    }


    /**
     * 获取token
     * 获取会话token，判断token是否在有效期内调用
     * @param $appId
     * @return string
     */
    public function get_token($appId)
    {
        $client_id = get_server_ip();

        $data = Cache::has('dg_'.$client_id.'_'.$appId);
        if ($data) {
            return Cache::get('dg_'.$client_id.'_'.$appId);
        } else {
            $req_data = [
                'timestamp' => $this->micTime(),
                'nonce' => $this->nonceStr(),
                'clientID' => $client_id,
                'appDeviceId' => $appId, //客户端唯一标识。h5访问时送手机唯一标识，其他可送appId
                'bizContent' => [
                    'devitk' => $this->getDevice($appId)
                ]
            ];
            $req_result = $this->curl($req_data, $this->getToken); //-1 系统错误    1 成功
            if ($req_result['status'] == 1) {
                $data = $req_result['data'];
                if ($data['errorCode'] == '0000') {
                    if ( isset($data['bizContent']) ) {
                        $bizContent = $data['bizContent'];
                        $token = $bizContent['token'];
                        $expireTime = $bizContent['expireTime'];
                        Cache::put('dg_'.$client_id.'_'.$appId, $token, $expireTime);

                        return $token;
                    } else {
                        Log::info('东莞银行-获取token-参数未返回');
                        Log::info($data);
                        return false;
                    }
                } else {
                    Log::info('东莞银行-获取token-请求失败');
                    Log::info($data);
                    return false;
                }
            } else {
                Log::info('东莞银行-获取token-请求失败');
                Log::info($req_result);
                return false;
            }
        }
    }


    /**
     * 随机数
     * @return string
     */
    public function nonceStr()
    {
        return md5(mt_rand(1000, 9999).time()).mt_rand(10000, 99999);
    }


    /**
     * 格式yyyyMMddhhmmssSSS 当前时间
     * @return string
     */
    public function micTime()
    {
        list($msec, $sec) = explode(' ', microtime());
        $msectime =  (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        $ts = date('YmdHis').substr($msectime, -3);

        return $ts;
    }


    //错误信息
    public function getMessageByCode($code)
    {
        switch ($code) {
            case '0001':    return '未开通支付权限';
                break;
            case '0002':    return '该支付方式不可用';
                break;
            case '0003':    return '短时间内提交太多订单';
                break;
            case '0004':    return '签名错误';
                break;
            case '0005':    return '商户号不存在';
                break;
            case '0006':    return '缺少参数';
                break;
            case '0007':    return '订单号重复';
                break;
            case '0008':    return '参数值错误';
                break;
            case '0009':    return '订单金额错误，只能是以分为单位的整数，不能带小数点';
                break;
            case '2001':    return '支付订单不存在';
                break;
            case '2002':    return '超过退款时间';
                break;
            case '2003':    return '不允许部分退款';
                break;
            case '2004':    return '存在未确定的退款订单';
                break;
            case '3000':    return 'token号不存在';
                break;
            case '8001':    return '透传银联的错误信息';
                break;
//            case '9999':    return '未知错误';
//                break;
            case '0000':    return '交易成功';
                break;
            case '1000':    return '检查设备指纹失败';
                break;
            case '1001':    return '解析客户端数据异常';
                break;
            case '1002':    return '客户端数字签名验证异常';
                break;
            case '1003':    return '响应客户端数据签名异常';
                break;
            case '1004':    return '客户端数据组包异常';
                break;
            case '1005':    return '下游系统异常';
                break;
            case '1006':    return '系统未知异常,请联系管理员';
                break;
            case '1007':    return '合作方密钥对生成失败,请联系管理员';
                break;
            case '1020':    return '网关通讯错误';
                break;
            case '1021':    return '后端系统异常';
                break;
            case '1040':    return 'token已过期，请重新获取';
                break;
            case '1050':    return '必填字段[nonce]or[timestamp]不允许为空';
                break;
//            case '1050':    return '必填字段[timestamp]不允许为空';
//                break;
            case '1051':    return '客户端随机数nonce重复,请重新生成';
                break;
            case '1060':    return '合作方设备信息认证失败';
                break;
            case '1061':    return '合作方私钥加密失败';
                break;
            case '1062':    return 'H5设备指纹上报失败';
                break;
            case '1071':    return '请求报文缺失 token, 请确认请求报文结构';
                break;
            case '2999':    return '字符集错误';
                break;
            case '9001':    return '缺失客户端配置';
                break;
            case '9999':    return '客户端解包异常';
                break;
            default:    return '莞银通未知错误';
        }
    }


    /**
     * 获取设备信息 -1 系统错误    1 成功    2 失败
     * @param $appId
     * @return string
     */
    public function getDevice($appId)
    {
        try {
            $req_data = [
                'app_id' => $appId,
                'userid' => $appId
            ];
            $url = 'https://127.0.0.1:15101/xindun/get_device_token';
            $result = $this->curl($req_data, $url); //-1 系统错误    1 成功

            if ( isset($result['status']) ) {
                if ($result['status'] == 0) {
                    return $result['resp'];
                }
                elseif ($result['status'] == -5001) {
                    Log::info('东莞银行-获取设备信息-参数错误');
                    Log::info($result);
                    return '';
                }
                else {
                    Log::info('东莞银行-获取设备信息-未知错误');
                    Log::info($result);
                    return '';
                }
            }
            else {
                Log::info('东莞银行-获取设备信息-请求失败');
                Log::info($result);
                return '';
            }
        } catch (\Exception $ex) {
            Log::info('东莞银行-获取设备信息-error');
            Log::info($ex->getMessage().' | '.$ex->getLine());
            return '';
        }
    }


    /**
     * curl请求 -1 系统错误    1 成功
     * @param array $data
     * @param string $Url
     * @return array
     */
    public function curl($data, $Url)
    {
        $ch = curl_init($Url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        $reqJsonData = json_encode($data);
        Log::info('东莞银行-curl原始请求参数');
        Log::info($reqJsonData);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $reqJsonData); //JSON类型字符串
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取的信息以文件流的形式返回
        $header = [
            'Content-Type=application/json;charset=UTF-8',
            'Content-Length='.strlen($data)
        ];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        $res = curl_exec($ch);
        $response = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($res == NULL) {
            curl_close($ch);
            return [
                'status' => -1,
                'message' => '请求错误'
            ];
        } else if ($response != "200") {
            curl_close($ch);
            return [
                'status' => -1,
                'message' => '请求错误'
            ];
        }

        curl_close($ch);
        $result = json_decode($res, true);

        return [
            'status' => 1,
            'message' => '成功',
            'data' => $result
        ];
    }


}
